// PatchView.m by Mara Helmuth

#import <math.h>
#import <appkit/Application.h>
#import <appkit/NXImage.h>
#import <appkit/graphics.h>
#import <appkit/Form.h>
#import <appkit/Panel.h>
#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>

#import "PatchWindow.h"
#import "PatchView.h"
#import "draw.h"
#import "Oscil.h"
#import "Arith.h"
#import "Rand.h"
#import "Out.h"
#import "Evp.h"
#import "Converter.h"
#import "Buzz.h"
#import "Reson.h"
#import "Comb.h"
#import "Pluck.h"
#import "Allpass.h"
//#import "Delay.h"
//#import "Reverb.h"
//#import "WS.h"
#import "Instrum.h"
#import "Param.h"


@implementation PatchView

- initFrame:(const NXRect *)frameRect
{
    [super initFrame:frameRect];
    
    /* off-screen buffer */
    screenImage = [[NXImage alloc] initSize:&bounds.size];
        
    return self;
}


- setImages		//  erase the patchview and make ugen images
{

	[self erase:0];
	
	connecting = NO;
	moving = NO;
	setting = NO;
	
	PSInit();	// initialize postscript drawing defs
	
   	return self;
}

/* instance methods */

- windowChanged:newWindow
{
    NXRect    rect;
    
    [super windowChanged:newWindow];
    
  /* if our new window's an PatchWindow, we'll "register" ourself with it */
    if ([newWindow respondsTo:@selector(registerRect:forView:)]) {
      /* give the window our window-based coordinates */
	rect = bounds;
	[self convertRect:&rect toView:nil];
	[newWindow registerRect:&rect forView:self];
    }
    
    return self;
}

- setUgen:(int)uType
{
	ugenType = uType;
    	return self;
}


- setDelegate:anObject
{
    	delegate = anObject;
    	return self;
}

- (BOOL)windowEntered:dragSource
{
 /* let our window know we've done some drawing */
    return YES;
}

- (BOOL)windowExited:dragSource	
{
  /* let our window know we've done some drawing */
    return YES;
}

- (BOOL)windowDropped:dragSource:(NXPoint *)currentLocation
{
  /* the user dropped a window into us */
    [window flushWindow];
    if ([delegate respondsTo:@selector(acceptedWindow:fromSource:)]) {
	[delegate acceptedWindow:self fromSource:dragSource];
    }
 
	// put the new ugen into the UnitGen ugenList
	switch(ugenType) {
		case 5: {
			currentUgen = [[Arith alloc] init];
			[currentUgen setAtype:'+'];
			break;
		}
		case 6: {
			currentUgen = [[Arith alloc] init];
			[currentUgen setAtype:'-'];
			break;
		}
		case 7: {
			currentUgen = [[Arith alloc] init];
			[currentUgen setAtype:'*'];
			break;
		}
		case 8: {
			currentUgen = [[Arith alloc] init];
			[currentUgen setAtype:'/'];
			break;
		}
/*
		case 9: {
			currentUgen = [[WS alloc] init];
			break;
		}
*/
		case 10: {
			currentUgen = [[Comb alloc] init];
			break;
		}
		case 11: {
			currentUgen = [[Pluck alloc] init];
			break;
		}
/*		case 12: {
			currentUgen = [[Allpass alloc] init];
			break;
		}
*/		case 13: {
			currentUgen = [[Allpass alloc] init];     // delay actually?
			break;
		}
/*		case 14: {
			currentUgen = [[Reverb alloc] init];
			break;
		}
*/
		case 15: {
			currentUgen = [[Oscil alloc] init];
			break;
		}
		case 16: {
			currentUgen = [[Buzz alloc] init];
			break;
		}
		case 17: {
			currentUgen = [[Rand alloc] init];
			break;
		}
		case 18: {
			currentUgen = [[Evp alloc] init];
			break;
		}
		case 19: {
			currentUgen = [[Reson alloc] init];
			break;
		}
		case 20: 
		case 21: 
		case 22: 
		case 23: 
		case 24: 
		case 25:  	{
			currentUgen = [[Converter alloc] init];
			[currentUgen setCtype:ugenType];
			break;
		}
		default: {
			NXRunAlertPanel("patchmix","Sorry, this unit generator is not yet working","OK",NULL,NULL);
			return NO;
		}
	}
	currentUgenCenter.x = currentUgenCenter.y = 40;
  /* we accept it */
    [self convertPoint:currentLocation fromView:nil]; 

   // center on the cursor 
    currentLocation->x  -= [currentUgen getCenter]->x;
    currentLocation->y  -= [currentUgen getCenter]->y;

	[currentUgen move:currentLocation];
	currentLocation = [currentUgen getLocation];

	currentImage = [currentUgen getImage];
    // draw the current ugen into the view 
    [self lockFocus];
    [currentImage composite:NX_SOVER toPoint:currentLocation];
    [self unlockFocus];
    [window flushWindow];
 
    // draw the current ugen into the off-screen buffer 
    if ([screenImage lockFocus]) {
	[currentImage composite:NX_SOVER toPoint:currentLocation];
	[screenImage unlockFocus];
    }

	
	[window disableFlushWindow];
    	[self display];
    	[window reenableFlushWindow];

    	return YES;
}

- drawSelf:(NXRect *)rects :(int)count
{
    /* load the appropriate portion of the off-screen buffer
     * into the visible portion of the view
     */
    [screenImage composite:NX_COPY fromRect:rects toPoint:&(rects->origin)];
    return self;
}


- erase:sender
//	erase the view and free up all the unit generators 
{
	
    NXSize	imageSize;
	NXPoint outLocation;
	
	setting = connecting = moving = NO;
	
    if ([screenImage lockFocus]) {
	NXEraseRect(&bounds);
	[screenImage unlockFocus];
    }

	[Inst freeUgens]; 	// free up ugens in list
		
	currentUgen = [[Out alloc] init];

    // draw the out ugen into the view
	currentImage = [currentUgen getImage];
    	[currentImage getSize:&imageSize];
	outLocation.x = floor((NX_WIDTH(&bounds) - imageSize.width) / 2.0);
   	outLocation.y = floor((NX_HEIGHT(&bounds) - imageSize.height) / 5.0);
	[currentUgen move:&outLocation];	
    	[self lockFocus];
    	[currentImage composite:NX_SOVER toPoint:&outLocation];
    	[self unlockFocus];
    	[window flushWindow];
 
	// draw the out ugen into the off-screen buffer 
    	if ([screenImage lockFocus]) {
		[currentImage composite:NX_SOVER toPoint:&outLocation];
		[screenImage unlockFocus];
    	}
    	[self display];
 
	
    	return self;	
}

- mouseDown:(NXEvent *)e
/*	logic for mouse down events:
	convert the point location 
	if a unit generator found at the mouse down point
		if a parameter found at the mouse down point
			if we are setting a parameter
				set the parameter to whatever use input into Param form field
				unhighlight the parameter
			else if the event is a double click
				we are setting a parameter so highlight it
				diplay the parameter name and value in the Param form field
			else
				we are connecting unit generators so save the location
		else (a unit generator found, but no parameter)
			if event is a double-click
				delete unit generator if user confirms and it is not an out ugen
			else single-click
				if setting, user error - display panel
				else we are moving a unit generator - erase and redraw at location
					and erase all its connections
	else no unit generator found
		if setting - user error - display panel
	while event is not a mouseUp,
		call mouseDraggedAction (for moving unit generator)
*/
 {
 	id pList;
    	NXPoint startPoint = e->location, nextPoint;
    	int looping = YES, i;
   	int oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
	
    	/* do first mouse down action */
    	[self convertPoint:&startPoint fromView:nil];    
//printf("mouseDown\n");

	if(currentUgen = [Inst findUgenAtPoint:&startPoint]) {
		currentImage = [currentUgen getImage];
		if(currentParam = [currentUgen findParamAtPoint:&startPoint]) {
			if(setting) {		// mouse click after set param val
				[currentParam setValue:[paramVal stringValueAt:0]];
				setting = NO;
				hrect = [currentParam getRect];  
							// unhighlight param
    				[self lockFocus];
    				NXHighlightRect(hrect);
    				[self unlockFocus];
    				[window flushWindow];
			}
			else if(e->data.mouse.click == 2) {         // double click, 
		// begin to set param...
				connecting = moving = NO;
				setting = YES;
				hrect = [currentParam getRect];		// highlight the param
    				[self lockFocus];
    				NXHighlightRect(hrect);
    				[self unlockFocus];
    				[window flushWindow];
				[paramVal setStringValue:[currentParam getValue] at:0];
				[paramVal setTitle:[currentParam getTitle] at:0];
				[paramVal selectTextAt:0];
			}
			else {						     // no double click
				connecting = YES;
				moving = setting = NO;	
				connPoint1 = [currentParam getDrawPoint];
				connUgen1 = currentUgen;
				connParam1 = currentParam;
			}
		}
		else {				// no param found,  but a ugen selected
			if(e->data.mouse.click == 2) { 		// double click, delete ugen?
				ugenselected = YES;
				hrect = [currentUgen getRect];	// highlight the ugen
    				[self lockFocus];
    				NXHighlightRect(hrect);
    				[self unlockFocus];
    				[window flushWindow];
				choice = NXRunAlertPanel("Cut Alert", "Delete this unit generator?", "Blow it away","No, Save it!",NULL);
				hrect = [currentUgen getRect];  
	// unhighlight ugen
    				[self lockFocus];
    				NXHighlightRect(hrect);
    				[self unlockFocus];
    				[window flushWindow];
				if(choice == NX_ALERTDEFAULT) {
					if(!strcmp("Out",[currentUgen getType])) 
						NXRunAlertPanel("Cut Alert", "Can't delete Output Unit Generator!", "OK", NULL, NULL);
					else {
						if(![currentUgen remove]) {
							NXRunAlertPanel("Cut Alert", "Can't delete a connected unit generator! Please delete connectors first.", "OK", NULL, NULL);
						}
						else {
							//THIS ERASES A UGEN 
    							// draw the current ugen into the view 
    							[self lockFocus];
    							[[currentUgen getImage] composite:NX_XOR toPoint:[currentUgen getLocation]];
   							[self unlockFocus];
    							[window flushWindow];
    							// draw the current ugen into the off-screen buffer 
    							if ([screenImage lockFocus]) {
    								[[currentUgen getImage] composite:NX_XOR toPoint:[currentUgen getLocation]];
								[screenImage unlockFocus];
							}
						}
					}
    				}
   				[self display];
			}
			else {	// not a double-click, ugen selected
				if(setting)		{	// unhighlight param and stop setting - user error
    					[self lockFocus];
    					NXHighlightRect(hrect);
    					[self unlockFocus];
    					[window flushWindow];
					NXRunAlertPanel("Param Alert", "I hope you know you have to click on the param again to save the value...", "Oh, alright!", NULL, NULL);
					setting = NO;
				}
				else 		{	// unit gen selected no param, not double click, not setting
					moving = YES;
					connecting = setting = NO;
							// erase the original so you can move image around
    					[self lockFocus];
    					[[currentUgen getImage] composite:NX_XOR toPoint:[currentUgen getLocation]];
   					[self unlockFocus];
    					[window flushWindow];
    							// draw the current ugen into the off-screen buffer 
    					if ([screenImage lockFocus]) {
    						[[currentUgen getImage] composite:NX_XOR toPoint:[currentUgen getLocation]];
						[screenImage unlockFocus];
					}
								// erase the connectors too, for now
					pList = [currentUgen getParamList];
					for(i = 0; i < [pList  count]; i++)   {
						currentParam = [pList objectAt:i];
						if(eraseParam = [currentParam getConnectedParam])      {    // if connected
								ePoint1 = [currentParam getDrawPoint];
								ePoint2 = [eraseParam getDrawPoint];
   								[self lockFocus];
 								PSsetgray(NX_WHITE);
								PSLine(ePoint1->x,ePoint1->y, ePoint2->x-ePoint1->x, ePoint2->y-ePoint1->y);
								[self unlockFocus];
   								if ([screenImage lockFocus]) {
 									PSsetgray(NX_WHITE);
									PSLine(ePoint1->x,ePoint1->y, ePoint2->x-ePoint1->x, ePoint2->y-ePoint1->y);
									[screenImage unlockFocus];
								}
						}
					}
								// draw ugen where mouse down is and store points
    					nextPoint.x  = startPoint.x - currentUgenCenter.x;
     					nextPoint.y  = startPoint.y - currentUgenCenter.y;
    						// draw the current ugen into the view 
    					[self lockFocus];
		  		 	[currentImage composite:NX_SOVER toPoint:&nextPoint];
    					[self unlockFocus];
    					[window flushWindow];
   					 if ([screenImage lockFocus]) {
							[currentImage composite:NX_SOVER toPoint:&nextPoint];
							[screenImage unlockFocus];
    					}
					erasePoint1.x = nextPoint.x;
					erasePoint1.y = nextPoint.y;
				}
			}
		}
	}
	// no ugen found
	else {	
		if(setting)		{	// unhighlight param and stop setting - user error
    			[self lockFocus];
    			NXHighlightRect(hrect);
    			[self unlockFocus];
    			[window flushWindow];
			NXRunAlertPanel("Param Alert", "I hope you know you have to click on the param again to save the value...", "Oh, alright!", NULL, NULL);
			setting = NO;
		}
	}	

   	while (looping)		{
		e = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
		nextPoint = e->location;		
    		[self convertPoint:&nextPoint fromView:nil]; 
		if(e->type == NX_MOUSEDRAGGED)	{
	    		[superview autoscroll:e];
									/* TO DRAG AN IMAGE:  */
			if(moving)	 {		// erase the old
    				[self lockFocus];
    				[currentImage composite:NX_XOR toPoint:&erasePoint1];
    				[self unlockFocus];
    				[window flushWindow];
    			// erase off-screen buffer, not needed if not drawing there 
    				if ([screenImage lockFocus]) {
					[currentImage composite:NX_XOR toPoint:&erasePoint1];
					[screenImage unlockFocus];
    				}
	    		// do continuing mouse dragged action 
	    			[self mouseDraggedAction:&nextPoint];
			}
		}
		else {			// 	mouse up - drag is done
	    		looping = NO;
	    							/* do mouse up action */
	    		[self mouseUpAction:&nextPoint];
    			[window setEventMask:oldMask];
		}	
	
	}
    	return self;
 }

- mouseDraggedAction:(NXPoint *)currentLocation
{
//	THIS DRAGS A UGEN:   draws ugen on new points  */
	if (moving) 	{
    		currentLocation->x  -= currentUgenCenter.x;
    		currentLocation->y  -= currentUgenCenter.y;
    			// draw the current ugen into the view 
    		[self lockFocus];
   		[currentImage composite:NX_SOVER toPoint:currentLocation];
    		[self unlockFocus];
    		[window flushWindow];
			//  draw the current ugen into the off-screen buffer 
   		 if ([screenImage lockFocus]) {
				[currentImage composite:NX_SOVER toPoint:currentLocation];
				[screenImage unlockFocus];
    		}
		erasePoint1.x = currentLocation->x;
		erasePoint1.y = currentLocation->y;
	}
	return self;
}

- mouseUpAction:(NXPoint *)currentLocation
/*	if moving
		redraw the connections to other unit generators
	else if connecting
		for each parameter:
			erase previous connections
			draw the new connection
			store the new connections
*/		
{
	id pList;
	BOOL draw = YES;
	int i;
	
	if(moving) 	{	
					//	for each param, redraw if connected
					//	save new location for Ugen
    		currentLocation->x  -= currentUgenCenter.x;
    		currentLocation->y  -= currentUgenCenter.y;
		[currentUgen move:currentLocation];
					//	if ugen connected, redraw connectors
					//		for each param, check if connected, redraw line
		pList = [currentUgen getParamList];
		for(i = 0; i < [pList  count]; i++)	{
			currentParam = [pList objectAt:i];
			if(connParam1 = [currentParam getConnectedParam])      {    // if connected
					connPoint1 = [currentParam getDrawPoint];
					connPoint2 = [connParam1 getDrawPoint];
   					[self lockFocus];
					PSLine(connPoint1->x,connPoint1->y, connPoint2->x-connPoint1->x, connPoint2->y-connPoint1->y);
					[self unlockFocus];
   					if ([screenImage lockFocus]) {
						PSLine(connPoint1->x,connPoint1->y, connPoint2->x-connPoint1->x, connPoint2->y-connPoint1->y);
						[screenImage unlockFocus];
					}
			}
		}
		[self display];
		moving = NO;
	}
//  if connecting draw final line
	else if(connecting) {
		if((currentUgen = [Inst findUgenAtPoint:currentLocation]) != connUgen1) {
					// test if these connectors are already connected
					// 	if so, erase the line 
					// erase any previous connections for either connector
			if(currentParam = [currentUgen findParamAtPoint:currentLocation]) {
				connPoint2 = [currentParam getDrawPoint];
				if([connParam1 getConnectedParam] == currentParam) {
									// they are already connected, ERASE
									// and disconnect
									//printf("already conn, erasing\n");
					[connParam1 setConnectedParam:nil];
					[currentParam setConnectedParam:nil];
   					[self lockFocus];
 					PSsetgray(NX_WHITE);
					PSLine(connPoint1->x,connPoint1->y, connPoint2->x-connPoint1->x, connPoint2->y-connPoint1->y);
					[self unlockFocus];
   					if ([screenImage lockFocus]) {
 						PSsetgray(NX_WHITE);
						PSLine(connPoint1->x,connPoint1->y, connPoint2->x-connPoint1->x, connPoint2->y-connPoint1->y);
						[screenImage unlockFocus];
					}
					draw = NO;
				}							// conn1 was connected, erase
				else  {
				   if(eraseParam = [connParam1 getConnectedParam]) {
					ePoint2 = [eraseParam getDrawPoint];
					[connParam1 setConnectedParam:nil];
					[eraseParam setConnectedParam:nil];
   					[self lockFocus];
 					PSsetgray(NX_WHITE);
					PSLine(connPoint1->x,connPoint1->y, ePoint2->x-connPoint1->x, ePoint2->y-connPoint1->y);
					[self unlockFocus];
   					if ([screenImage lockFocus]) {
 						PSsetgray(NX_WHITE);
						PSLine(connPoint1->x,connPoint1->y, ePoint2->x-connPoint1->x, ePoint2->y-connPoint1->y);
						[screenImage unlockFocus];
					}
				   }						// conn2 was connected, erase
				   if(eraseParam = [currentParam getConnectedParam]) {
					ePoint1 = [eraseParam getDrawPoint];
					[currentParam setConnectedParam:nil];
					[eraseParam setConnectedParam:nil];
   					[self lockFocus];
 					PSsetgray(NX_WHITE);
					PSLine(ePoint1->x,ePoint1->y, connPoint2->x-ePoint1->x, connPoint2->y-ePoint1->y);
					[self unlockFocus];
   					if ([screenImage lockFocus]) {
 						PSsetgray(NX_WHITE);
						PSLine(ePoint1->x,ePoint1->y, connPoint2->x-ePoint1->x, connPoint2->y-ePoint1->y);
						[screenImage unlockFocus];
					}
				   }
				}	
				if(draw) {				// draw the connnector
					[connParam1 setConnectedParam:currentParam];
					[currentParam setConnectedParam:connParam1];
   					[self lockFocus];
					PSLine(connPoint1->x,connPoint1->y, connPoint2->x-connPoint1->x, connPoint2->y-connPoint1->y);
					[self unlockFocus];
   					if ([screenImage lockFocus]) {
						PSLine(connPoint1->x,connPoint1->y, connPoint2->x-connPoint1->x, connPoint2->y-connPoint1->y);
						[screenImage unlockFocus];
    					}
				}
			}
			[self display];
			connecting = NO;
		}
	}
    	return self;
}

/* delegation methods */
- acceptedWindow:acceptView fromSource:source
{
  /*
   * if delegate implements this method, it'll get called whenever the user
   * drops a window into the view
   */
    return self;
}

@end
